/*
  author Sylvain Bertrand <digital.ragnarok@gmail.com>
  Protected by GNU Affero GPL v3 with some exceptions.
  See README at root of alga tree.
*/
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/fs.h>
#include <linux/irq.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <asm/ioctl.h>
#include <linux/dma-direction.h>

#include <alga/rng_mng.h>
#include <alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/amd/dce4/dce4.h>
#include <alga/amd/evergreen/ioctl.h>

#include "regs.h"

#include "ih.h"
#include "ba.h"
#include "ucode.h"
#include "cp.h"
#include "drv.h"

dev_t devt; /* global to the driver */

static int open(struct inode *i, struct file *f)
{
	struct dev_drv_data *dd;
	//DEBUG
	printk(KERN_INFO "EVERGREEN OPEN\n");
	dd = container_of(i->i_cdev, struct dev_drv_data, evergreen_cdev);
	f->private_data = dd->dev;
	return 0;
}

static int dce_dps_info(struct pci_dev *dev, void __user *user)
{
	struct dev_drv_data *dd;
	int r;
	struct evergreen_dce_dps_info *info;


	/* too big for a kernel stack */
	info = kzalloc(sizeof(*info), GFP_KERNEL);
	if (!info)
		return -ENOMEM;

	dd = pci_get_drvdata(dev);

	r = dce4_dps_info(dd->dce, &info->used, &info->connected,
					&info->pixel_fmts, &info->timings);
	if (r != 0) {
		r = -ENODEV;
		goto free_info;
	}
	if(copy_to_user(user, info, sizeof(*info)))
		r = -EFAULT;

free_info:
	kfree(info);
	return r;
}

static int dce_dp_set(struct pci_dev *dev, void __user *user)
{
	struct dev_drv_data *dd;
	struct evergreen_dce_dp_set dp_set;
	struct db_fb db_fb;
	int r;

	if(copy_from_user(&dp_set, user, sizeof(dp_set)))
		return -EFAULT;

	db_fb.primary = dp_set.primary;
	db_fb.secondary = dp_set.secondary;
	db_fb.pixel_fmt = dp_set.pixel_fmt;
	db_fb.timing = dp_set.timing;
	db_fb.pitch = dp_set.pitch;

	dd = pci_get_drvdata(dev);

	r = dce4_sink_mode_set(dd->dce, dp_set.i, &db_fb);
	if (r != 0)
		return -ENODEV;
	return 0;
}

static int dce_dp_dpm(struct pci_dev *dev, unsigned __user *user_i)
{
	struct dev_drv_data *dd;
	unsigned i;
	int r;

	get_user(i, user_i);

	dd = pci_get_drvdata(dev);

	r = dce4_dp_dpm(dd->dce, i);
	if (r != 0)
		return -ENODEV;
	return 0;
}

static int dce_pf(struct pci_dev *dev, unsigned __user *user)
{
	struct dev_drv_data *dd;
	unsigned i;
	unsigned vblanks_n;
	int r;

	get_user(i, user);

	dd = pci_get_drvdata(dev);

	r = dce4_pf(dd->dce, i, &vblanks_n);
	if (r != 0)
		return -ENODEV;
	put_user(vblanks_n, user);
	return 0;
}

static int evergreen_mem_alloc(struct pci_dev *dev,
					struct evergreen_mem  __user *mem)
{
	struct dev_drv_data *dd;
	u64 sz;
	u64 align;
	u64 gpu_addr;
	int r;

	get_user(sz, &mem->sz);
	get_user(align, &mem->align);

	dd = pci_get_drvdata(dev);

	r = rng_alloc_align(&gpu_addr, &dd->vram.mng, sz, align); 
	if (r == 0)
		put_user(gpu_addr, &mem->gpu_addr);
	else
		r = -ENODEV;
	return r;
}

static void evergreen_mem_free(struct pci_dev *dev, u64  __user *user_gpu_addr)
{
	u64 gpu_addr;
	struct dev_drv_data *dd;

	get_user(gpu_addr, user_gpu_addr);

	dd = pci_get_drvdata(dev);

	rng_free(&dd->vram.mng, gpu_addr);
}

static long unlocked_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{
	struct pci_dev *dev;
	int r;
	unsigned nr;

	dev = f->private_data;
	nr = _IOC_NR(cmd);

	r = 0;
	switch (nr) {
	case EVERGREEN_DCE_DPS_INFO:
		//DEBUG
		printk(KERN_INFO "IOCTL_EVERGREEN_DCE_DPS_INFO\n");
		r = dce_dps_info(dev, (void __user *)arg);
		break;
	case EVERGREEN_DCE_DP_SET:
		//DEBUG
		printk(KERN_INFO "EVERGREEN_DCE_DP_SET\n");
		r = dce_dp_set(dev, (void __user *)arg);
		break;
	case EVERGREEN_DCE_DP_DPM:
		//DEBUG
		printk(KERN_INFO "EVERGREEN_DCE_DP_DPM\n");
		r = dce_dp_dpm(dev, (unsigned __user *)arg);
		break;
	case EVERGREEN_DCE_PF:
		//DEBUG
		printk(KERN_INFO "EVERGREEN_DCE_PF\n");
		r = dce_pf(dev, (unsigned __user *)arg);
		break;
	case EVERGREEN_MEM_ALLOC:
		//DEBUG
		printk(KERN_INFO "EVERGREEN_MEM_ALLOC\n");
		r = evergreen_mem_alloc(dev,
					(struct evergreen_mem __user *)arg);
		break;
	case EVERGREEN_MEM_FREE:
		//DEBUG
		printk(KERN_INFO "EVERGREEN_MEM_FREE\n");
		evergreen_mem_free(dev, (u64  __user *)arg);
		break;
	case EVERGREEN_INFO:
		//DEBUG
		printk(KERN_INFO "EVERGREEN_INFO\n");
		break;
	default:
		r = -EINVAL;
		break;
	}
	return r;
}

static int release(struct inode *i, struct file *f)
{
	//DEBUG
	printk(KERN_INFO "EVERGREEN RELEASE\n");
	return 0;
}

struct file_operations fops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = unlocked_ioctl,
	.open = open,
	.release = release,
};
